
#include <new>
#include <apr-1/apr_pools.h>
#include <apr-1/apr_strings.h>
#include "../iface/debug.h"
#include "../iface/APRInterface.h"
#include "../utils/StringUtils.h"
#include "../net/netgeneral.h"
#include "EncMessage.h"

EncMessage::EncMessage(apr_pool_t *imp)
{
   highmp = imp;

   apr_status_t status;
   status = apr_pool_create(&mp, highmp);
   CheckAPRError(status);

   recipient = NULL;
   sender = NULL;
   htable = NULL;
   aux_htable = NULL;
   messagetype = NULL;
   processee = NULL;

   headers = NULL;
   aux_headers = NULL;
   payloads = NULL;
   lengths = NULL;

   // Sign messages by default
   willsign = 1;
   // Is signed - so, has been signed and verified
   issigned = 0;
   // Remove sending key - dangerous!
   removesendingkey = 0;
}

EncMessage::~EncMessage()
{
   if (payloads)
   {
      for (int k = 0; k < payloads->size(); k++)
      {
         int slen = strlen(payloads->at(k));
         memset((void *)payloads->at(k), 0, sizeof(char)*slen);
      }
   }
   if (payloads) payloads->~vector<const char *>();
   payloads = NULL;
   if (lengths) lengths->~vector<int>();
   lengths = NULL;
   if (headers) headers->~vector<const char *>();
   headers = NULL;
   if (aux_headers) aux_headers->~vector<const char *>();
   aux_headers = NULL;
   apr_pool_destroy(mp);
}

void EncMessage::ReplyTo(EncMessage *imsg)
{
   SetRecipient(imsg->GetSender());
   SetSender(imsg->GetRecipient());
}

void EncMessage::SetRecipient(const char *iformat, ...)
{
   va_list args; va_list newcopy;
   va_start(args, iformat); va_copy(newcopy, args);
   recipient = StringUtils::mpprintf(mp, iformat, newcopy);
   va_end(newcopy); va_end(args);
   StringUtils::aggressiveStringSanitiser((char *)recipient);
}

void EncMessage::SetSender(const char *iformat, ...)
{
   va_list args; va_list newcopy;
   va_start(args, iformat); va_copy(newcopy, args);
   sender = StringUtils::mpprintf(mp, iformat, newcopy);
   va_end(newcopy); va_end(args);
   StringUtils::aggressiveStringSanitiser((char *)sender);
}

void EncMessage::AddTypeHeaderNOCRLF(const char *itype, const char *iprocessee)
{
   if (headers)
   {
      errprint("headers already specified");
      return;
   }
   messagetype = apr_pstrdup(mp, itype);
   processee = apr_pstrdup(mp, iprocessee);
   StringUtils::aggressiveStringSanitiser((char *)processee);
   void *headers_mem = (void *)apr_palloc(mp, sizeof(std::vector<const char *>));
   headers = new(headers_mem) std::vector<const char *>();
   headers->push_back(apr_pstrcat(mp, itype, " ", iprocessee, NULL));
}

void EncMessage::AddHeaderLineNOCRLF(const char *iformat, ...)
{
   if (!headers)
   {
      errprint("must specify message type specific header first");
      return;
   }
   va_list args; va_list newcopy;
   va_start(args, iformat); va_copy(newcopy, args);
   headers->push_back(StringUtils::mpprintf(mp, iformat, newcopy));
   va_end(newcopy); va_end(args);
}

void EncMessage::AddPayloadString(const char *iformat, ...)
{
   if (iformat == NULL) { errprint("format string sent was NULL"); return; }
   if (strlen(iformat) == 0) { errprint("format string sent was zero length"); return; }
   if (!payloads)
   {
      void *payloads_mem = (std::vector<const char *> *)apr_palloc(mp, sizeof(std::vector<const char *>));
      void *lengths_mem = (std::vector<int> *)apr_palloc(mp, sizeof(std::vector<int>));
      payloads = new(payloads_mem) std::vector<const char *>();
      lengths = new(lengths_mem) std::vector<int>();
   }
   va_list args; va_list newcopy;
   va_start(args, iformat); va_copy(newcopy, args);
   const char *newline = StringUtils::mpprintf(mp, iformat, newcopy);
   va_end(newcopy); va_end(args);
   payloads->push_back(newline);
   lengths->push_back(strlen(newline));
}

void EncMessage::AddVerbatimPayloadString(const char *istring)
{
   if (istring == NULL) { errprint("string sent was NULL"); return; }
   if (strlen(istring) == 0) { errprint("string sent was zero length"); return; }
   if (!payloads)
   {
      void *payloads_mem = (std::vector<const char *> *)apr_palloc(mp, sizeof(std::vector<const char *>));
      void *lengths_mem = (std::vector<int> *)apr_palloc(mp, sizeof(std::vector<int>));
      payloads = new(payloads_mem) std::vector<const char *>();
      lengths = new(lengths_mem) std::vector<int>();
   }
   const char *newline = apr_pstrdup(mp, istring);
   payloads->push_back(newline);
   lengths->push_back(strlen(newline));
}

void EncMessage::AddPayloadBuffer(const char *ibuffer, int ilength)
{
   if (!payloads)
   {
      void *payloads_mem = (std::vector<const char *> *)apr_palloc(mp, sizeof(std::vector<const char *>));
      void *lengths_mem = (std::vector<int> *)apr_palloc(mp, sizeof(std::vector<int>));
      payloads = new(payloads_mem) std::vector<const char *>();
      lengths = new(lengths_mem) std::vector<int>();
   }
   void *memblock = (void *)apr_palloc(mp, ilength+1);
   memcpy(memblock, ibuffer, ilength);
   ((char *)memblock)[ilength] = '\0';
   payloads->push_back((const char *)memblock);
   lengths->push_back(ilength);
}

void EncMessage::ForceNoSignature()
{
   willsign = 0;
}

void EncMessage::ForceDestroySendingKey()
{
   removesendingkey = 1;
}

void EncMessage::Reset()
{
   recipient = NULL;
   sender = NULL;
   htable = NULL;
   messagetype = NULL;
   processee = NULL;

   if (payloads) payloads->~vector<const char *>();
   payloads = NULL;
   if (lengths) lengths->~vector<int>();
   lengths = NULL;
   if (headers) headers->~vector<const char *>();
   headers = NULL;
   if (aux_headers) aux_headers->~vector<const char *>();
   aux_headers = NULL;


   // Sign messages by default
   willsign = 1;
   // Is signed - so, has been signed and verified (by default no)
   issigned = 0;

   apr_pool_destroy(mp);

   apr_status_t status;
   status = apr_pool_create(&mp, highmp);
   CheckAPRError(status);
}

void EncMessage::SetWasSignedFlag()
{
   issigned = 1;
}

void EncMessage::ParseHeaderStream(const char *stream)
{
   if (headers)
   {
      errprint("headers already exist, cannot parse headers into this instance");
      return;
   }
   const char *firstheader_idxptr = strstr(stream, CRLF_STR);
   int offset = firstheader_idxptr - stream;
   char *firstheader_dup = (char *)apr_palloc(mp, sizeof(char)*(offset + 1));
   memcpy(firstheader_dup, stream, sizeof(char)*offset);
   firstheader_dup[offset] = '\0';
   char *remainder = NULL;
   messagetype = apr_strtok(firstheader_dup, " ", &remainder);
   processee = apr_strtok(NULL, " ", &remainder);
   char *dup_stream = apr_pstrdup(mp, stream);
   void *headers_mem = (void *)apr_palloc(mp, sizeof(std::vector<const char *>));
   headers = new(headers_mem) std::vector<const char *>();
   remainder = NULL;
   char *ttoken = apr_strtok(dup_stream, CRLF_STR, &remainder);
   while (ttoken != NULL)
   {
      if (ttoken[0] >= ' ') headers->push_back(ttoken);
      ttoken = apr_strtok(NULL, CRLF_STR, &remainder);
   }
   GenerateHeaderTable();
}

void EncMessage::GenerateHeaderTable()
{
   if (!headers)
   {
      errprint("no header table");
      return;
   }
   if (headers->size() == 0)
   {
      errprint("no header in the string list");
      return;
   }
   if (htable)
   {
      errprint("header table already exists");
      return;
   }
   htable = apr_table_make(mp, headers->size());
   for (int k = 0; k < headers->size(); k++)
   {
      char *remainder = NULL;
      char *dupstr = (char *)apr_pstrdup(mp, headers->at(k));
      const char *key_token = apr_strtok(dupstr, ": ", &remainder);
      const char *value_token = apr_strtok(NULL, ": " CRLF_STR, &remainder);
      while ((value_token[0] == ' ') || (value_token[0] == ':'))
         value_token = apr_strtok(NULL, ": " CRLF_STR, &remainder);
      apr_table_set(htable, key_token, value_token);
   }
}

const char *EncMessage::GetHeaderStream()
{
   if (!headers)
   {
      errprint("no header table");
      return NULL;
   }
   if (headers->size() == 0)
   {
      errprint("no header in the string list");
      return NULL;
   }
   // Prepare for the extra CRLFs and the NULL terminator
   int total_alloc = strlen(CRLF_STR)*headers->size() + 1;
   // Add all of the individual strlens
   for (int k = 0; k < headers->size(); k++)
      total_alloc += strlen(headers->at(k));
   int idx_position = 0;
   char *bigstream = (char *)apr_palloc(mp, sizeof(char)*total_alloc);
   bigstream[0] = '\0';
   for (int k = 0; k < headers->size(); k++)
   {
      strcat(bigstream, headers->at(k));
      strcat(bigstream, CRLF_STR);
   }
   return bigstream;
}

const char *EncMessage::GetRecipient()
{
   return recipient;
}

const char *EncMessage::GetSender()
{
   return sender;
}

const char *EncMessage::GetType()
{
   return messagetype;
}

const char *EncMessage::GetProcessee()
{
   return processee;
}

const int EncMessage::GetHowManyPayloads()
{
   return (payloads) ? payloads->size() : 0;
}

const char *EncMessage::GetPayload(int pnum)
{
   return payloads->at(pnum);
}

const int EncMessage::GetPayloadLength(int pnum)
{
   return lengths->at(pnum);
}

const int EncMessage::IsType(const char *ctype)
{
   if (!ctype) return 0;
   if (!messagetype) return 0;
   return (strcmp(ctype, messagetype) == 0);
}

const int EncMessage::IsPayloadCount(const int pcount)
{
   return (pcount == GetHowManyPayloads());
}

const int EncMessage::GuaranteedAuthentic()
{
   return issigned;
}

const int EncMessage::ToBeSigned()
{
   return willsign;
}

apr_pool_t *EncMessage::GetEncMessageAssociatedMemoryPool()
{
   return mp;
}

const apr_table_t *EncMessage::GetHeaderTable()
{
   return htable;
}

const apr_table_t *EncMessage::GetAuxHeaderTable(int payload, const char *linesep, const char *keyvalsep)
{
   if (aux_htable) return aux_htable;
   char *dup_stream = apr_pstrdup(mp, GetPayload(payload));
   char *combi_sep = apr_pstrcat(mp, keyvalsep, linesep, NULL);
   void *aux_headers_mem = (void *)apr_palloc(mp, sizeof(std::vector<const char *>));
   aux_headers = new(aux_headers_mem) std::vector<const char *>();
   char *remainder = NULL;
   char *ttoken = apr_strtok(dup_stream, linesep, &remainder);
   while (ttoken != NULL)
   {
      if (ttoken[0] >= ' ') aux_headers->push_back(ttoken);
      ttoken = apr_strtok(NULL, linesep, &remainder);
   }
   aux_htable = apr_table_make(mp, aux_headers->size());
   for (int k = 0; k < aux_headers->size(); k++)
   {
      char *remainder = NULL;
      char *dupstr = (char *)apr_pstrdup(mp, aux_headers->at(k));
      const char *key_token = apr_strtok(dupstr, keyvalsep, &remainder);
      const char *value_token = apr_strtok(NULL, combi_sep, &remainder);
      char vtok[2];
      vtok[0] = value_token[0]; vtok[1] = '\0';
      while (strstr(keyvalsep, vtok) != NULL)
      {
         value_token = apr_strtok(NULL, combi_sep, &remainder);
         vtok[0] = value_token[0];
      }
      apr_table_set(aux_htable, key_token, value_token);
   }
   return aux_htable;
}

const int EncMessage::GetAuxHeaderCount()
{
   if (aux_headers == NULL) return -1;
   return aux_headers->size();
}

const int EncMessage::WeMustDestroySendingKey()
{
   return removesendingkey;
}

void EncMessage::Print()
{
   printf("From: %s\n", sender);
   printf("To: %s\n", recipient);
   printf("Type: %s\n", messagetype);
   printf("Subject: %s\n", processee);
   if ((headers) && (headers->size() > 1))
   {
      printf("Other headers:\n");
      for (int i = 1; i < headers->size(); i++)
         printf("%s\n", headers->at(i));
   }
   if ((payloads) && (payloads->size() > 0))
   {
      for (int i = 0; i < payloads->size(); i++)
      {
         printf("Payload %d:\n", i);
         if (IsBinary(payloads->at(i), lengths->at(i)))
            printf("\n");//PrintHex(payloads->at(i), lengths->at(i));
         else
            printf("%s\n", payloads->at(i));
      }
   }
}

int EncMessage::IsBinary(const char *bindata, int length)
{
   int isbindata = 0;
   for (int k = 0; k < length; k++) isbindata |= (bindata[k] < '\0');
   return isbindata;
}

void EncMessage::PrintHex(const char *bindata, int length)
{
   for (int k = 0x00; k <= length; k+=0x10)
   {
      printf("0x%.5X:  ", k);
      for (int j = 0x00; j < 0x10; j++)
      {
         if ((k+j) < length)
            printf("%.2X ", (unsigned char)(bindata[k+j]));
         else
            printf("   ");
         if ((j & 0x3) == 0x3) printf(" ");
      }
      for (int j = 0x00; j < 0x10; j++)
      {
         if ((k+j) < length)
            printf("%c", ((bindata[k+j] < ' ') ? '.' : bindata[k+j]));
         else
            printf(" ");
      }
      printf("\n");
   }
}
